package models

import (
	"errors"
	"time"

	"gitee.com/laofa1/play/k_conf_mng/kconf_backend/pkg/util"
)

type ConfKey struct {
	ID        uint64    `json:"key"`
	Name      string    `json:"confName"`
	KeyType   uint      `json:"confType"`
	UserID    uint64    `json:"user_id"`
	CreatedAt time.Time `json:"createdAt"`
}

type ConfInstance struct {
	ID        uint64    `json:"id"`
	Name      string    `json:"name"`
	CreatedAt time.Time `json:"createdAt"`
}

type ConfKeyValue struct {
	KeyName  string
	KeyValue string
}

type ConfItem struct {
	ID         uint64 `json:"key"`
	KeyID      uint64
	Value      string `json:"confValue"`
	InstanceID uint64
	UpdatedAt  time.Time `json:"updatedAt"`
	UserID     uint64
}

type ConfItemRow struct {
	KeyID         uint64
	KeyName       string
	KeyType       uint
	KeyCreatedAt  time.Time
	KeyUser       string
	ItemID        uint64
	ItemValue     string
	Instance      string
	ItemUpdatedAt time.Time
	ItemUser      string
}

type ConfItemReal struct {
	ID        uint64    `json:"key"`
	Instance  string    `json:"instance"`
	ConfName  string    `json:"confName"`
	ConfValue string    `json:"confValue"`
	UpdatedAt time.Time `json:"updatedAt"`
	User      string    `json:"user"`
}

type ConfKeyReal struct {
	ID        uint64         `json:"key"`
	ConfName  string         `json:"confName"`
	ConfType  uint           `json:"confType"`
	CreatedAt time.Time      `json:"createdAt"`
	User      string         `json:"user"`
	Instances []ConfItemReal `json:"instances"`
}

type ConfTemplate struct {
	ID        uint64    `json:"id"`
	Name      string    `json:"name"`
	Content   string    `json:"content"`
	CreatedAt time.Time `json:"createdAt"`
	User      string    `gorm:"column:user_name" json:"user"`
}

func CreateBlankConfInstance(name string) (uint64, error) {
	if len(name) == 0 || len(name) > 50 {
		return 0, errors.New("instance name given is blank")
	}

	instance := ConfInstance{Name: name}
	result := db.Create(&instance)
	if result.Error != nil {
		return 0, result.Error
	}
	return instance.ID, nil
}

func CloneConfInstance(newName, oldName string, userid uint64) error {
	instanceNewID, err := CreateBlankConfInstance(newName)
	if err != nil {
		return err
	}

	// get old instance id
	var instanceOld ConfInstance
	result := db.Where(&ConfInstance{Name: oldName}).First(&instanceOld)
	if result.Error != nil {
		return result.Error
	}
	if result.RowsAffected != 1 {
		return errors.New("not old config instance find")
	}

	// get all ConfigItem of config instance with oldName
	var oldConfItems []ConfItem
	result = db.Where(&ConfItem{InstanceID: instanceOld.ID}).Find(&oldConfItems)
	if result.Error != nil {
		return result.Error
	}
	if result.RowsAffected < 1 {
		return errors.New("no conf items find with old instance name")
	}

	// replace all older instance id with new instance id and insert into config item
	var newConfItems []ConfItem
	for _, eachItem := range oldConfItems {
		newConfItem := ConfItem{
			Value:      eachItem.Value,
			KeyID:      eachItem.KeyID,
			InstanceID: instanceNewID,
			UserID:     userid,
		}
		newConfItems = append(newConfItems, newConfItem)
	}
	result = db.Create(&newConfItems)
	return result.Error
}

func DeleteInstance(instanceId uint64) error {
	instance := ConfInstance{ID: instanceId}
	result := db.Delete(&instance)
	if result.Error != nil {
		return result.Error
	}
	return nil
}

func GetInstances() ([]ConfInstance, error) {
	var instances []ConfInstance
	if result := db.Find(&instances); result.Error != nil {
		return instances, result.Error
	}
	return instances, nil
}

func CreateConfKeyValue(keyName, value string, userid uint64) (int64, error) {
	if len(keyName) < 1 || len(keyName) > 50 || len(value) > 512 {
		return 0, errors.New("length of conf keyname or length config value not valid")
	}
	// get all ConfInstance
	var confInstances []ConfInstance
	resultInstance := db.Find(&confInstances)
	if resultInstance.Error != nil {
		return 0, resultInstance.Error
	}
	if resultInstance.RowsAffected < 1 {
		return 0, errors.New("no config instance exists")
	}

	// create confKey
	confKey := ConfKey{Name: keyName, KeyType: util.GetKeyType(keyName), UserID: userid}
	resultConfKey := db.Create(&confKey)
	if resultConfKey.Error != nil {
		return 0, resultConfKey.Error
	}
	if resultConfKey.RowsAffected != 1 {
		return 0, errors.New("create confKey fail")
	}

	// create config items
	var confItems []ConfItem
	for _, eachInstance := range confInstances {
		confItems = append(confItems, ConfItem{KeyID: confKey.ID, Value: value, InstanceID: eachInstance.ID, UserID: userid})
	}
	resultKey := db.Create(&confItems)
	//resultKey := db.Session(&gorm.Session{FullSaveAssociations: true}).Create(&confKey)
	if resultKey.Error != nil {
		return 0, resultKey.Error
	}
	if resultKey.RowsAffected < 1 {
		return 0, errors.New("not create a conf key")
	}
	return resultKey.RowsAffected, nil
}

func GetConfItemsByInstanceId(id uint64) (map[string]string, error) {
	sqlQuery := `
		SELECT conf_keys.name as key_name, conf_items.value as key_value 
		FROM conf_items
		LEFT JOIN conf_keys ON conf_items.key_id = conf_keys.id
		WHERE conf_items.instance_id = ? AND conf_keys.key_type = ?
		ORDER BY conf_keys.name
	`

	var confItemsReplace []ConfKeyValue
	if result := db.Raw(sqlQuery, id, 1).Scan(&confItemsReplace); result.Error != nil {
		return nil, result.Error
	}

	// generate map
	confs := make(map[string]string, 100)
	for _, eachLine := range confItemsReplace {
		confs[eachLine.KeyName] = eachLine.KeyValue
	}

	return confs, nil
}

func GetConfItemsByInstanceName(name string) (map[string]string, error) {
	var confInstance ConfInstance
	if result := db.Where("name = ?", name).First(&confInstance); result.Error != nil {
		return nil, result.Error
	}
	return GetConfItemsByInstanceId(confInstance.ID)
}

func GetConfKeyValueByFilter(keyName string, keyType uint) ([]ConfKeyReal, error) {
	queryName := "%" + keyName + "%"

	sqlQueryPrefix := `
		SELECT conf_keys.id as key_id, conf_keys.name as key_name, conf_items.id as item_id, 
		conf_instances.name as instance, conf_keys.key_type as key_type, auths_key.user_name as key_user,
		conf_keys.created_at as key_created_at, conf_items.value as item_value, conf_items.updated_at as item_updated_at,
		auths.user_name as item_user 
		FROM conf_items 
		LEFT JOIN conf_keys ON conf_items.key_id = conf_keys.id 
		LEFT JOIN auths ON conf_items.user_id = auths.id 
		LEFT JOIN conf_instances ON conf_items.instance_id = conf_instances.id
        LEFT JOIN auths as auths_key ON conf_keys.user_id = auths_key.id
	`
	sqlQuerySuffix := " ORDER BY key_id, instance;"

	var confItemRows []ConfItemRow

	// use like cause to query gonna not using index, so if record size is large it'll be not efficient
	if keyName != "" && keyType != 0 {
		rows, err := db.Raw(sqlQueryPrefix+" WHERE conf_keys.name LIKE ? AND conf_keys.key_type = ?"+sqlQuerySuffix, queryName, keyType).Rows()
		if err != nil {
			return nil, err
		}
		defer rows.Close()
		var confItemRow ConfItemRow
		for rows.Next() {
			db.ScanRows(rows, &confItemRow)
			confItemRows = append(confItemRows, confItemRow)
		}
	} else if keyName != "" {
		rows, err := db.Raw(sqlQueryPrefix+" WHERE LOWER(conf_keys.name) LIKE LOWER(?)"+sqlQuerySuffix, queryName).Rows()
		if err != nil {
			return nil, err
		}
		defer rows.Close()
		var confItemRow ConfItemRow
		for rows.Next() {
			db.ScanRows(rows, &confItemRow)
			confItemRows = append(confItemRows, confItemRow)
		}
	} else if keyType != 0 {
		rows, err := db.Raw(sqlQueryPrefix+" WHERE conf_keys.key_type = ?"+sqlQuerySuffix, keyType).Rows()
		if err != nil {
			return nil, err
		}
		defer rows.Close()
		var confItemRow ConfItemRow
		for rows.Next() {
			db.ScanRows(rows, &confItemRow)
			confItemRows = append(confItemRows, confItemRow)
		}
	} else {
		rows, err := db.Raw(sqlQueryPrefix + sqlQuerySuffix).Rows()
		if err != nil {
			return nil, err
		}
		defer rows.Close()
		var confItemRow ConfItemRow
		for rows.Next() {
			db.ScanRows(rows, &confItemRow)
			confItemRows = append(confItemRows, confItemRow)
		}
	}

	// process confItemRow
	keyID := uint64(0)
	confItemReal := ConfItemReal{}
	confKeyReal := ConfKeyReal{}
	confKeyReals := []ConfKeyReal{}

	for i, eachRow := range confItemRows {
		// construct to item
		confItemReal = ConfItemReal{
			ID:        eachRow.ItemID,
			Instance:  eachRow.Instance,
			ConfName:  eachRow.KeyName,
			ConfValue: eachRow.ItemValue,
			UpdatedAt: eachRow.ItemUpdatedAt,
			User:      eachRow.ItemUser,
		}

		// get a new group begin
		if keyID != eachRow.KeyID {
			// to avoid first group being wrong append
			if len(confKeyReal.Instances) != 0 {
				confKeyReals = append(confKeyReals, confKeyReal)
			}
			// reset keyID
			keyID = eachRow.KeyID
			// create new ConfKeyReal
			confKeyReal.User = eachRow.KeyUser
			confKeyReal.ConfName = eachRow.KeyName
			confKeyReal.ID = eachRow.KeyID
			confKeyReal.ConfType = eachRow.KeyType
			confKeyReal.CreatedAt = eachRow.KeyCreatedAt
			confKeyReal.Instances = []ConfItemReal{confItemReal}
		} else {
			confKeyReal.Instances = append(confKeyReal.Instances, confItemReal)
		}

		// last one append to confKeyReals
		if i == len(confItemRows)-1 {
			confKeyReals = append(confKeyReals, confKeyReal)
		}
	}
	// just for debug to show result

	return confKeyReals, nil
}

func UpdateConfItem(id, userid uint64, value string) (uint64, error) {
	if valueLength := len(value); valueLength > 512 {
		return 0, errors.New("value length should less than 512")
	}
	confItem := ConfItem{ID: id}
	result := db.Model(&confItem).Updates(map[string]interface{}{
		"Value":  value,
		"UserID": userid,
	})

	if result.Error != nil {
		return 0, result.Error
	}
	if result.RowsAffected < 1 {
		return 0, errors.New("config item not updated")
	}
	return uint64(result.RowsAffected), nil
}

func GetConfTemplateContent(name string) (string, error) {
	if len(name) < 1 {
		return "", errors.New("config template name not given")
	}

	var confTemplate ConfTemplate
	result := db.Where(&ConfTemplate{Name: name}).Order("created_at desc").Limit(1).Find(&confTemplate)
	if result.Error != nil {
		return "", result.Error
	}
	return confTemplate.Content, nil
}

func GetTemplateById(id uint64) (ConfTemplate, error) {
	confTemplate := ConfTemplate{ID: id}
	if result := db.First(&confTemplate); result.Error != nil {
		return confTemplate, result.Error
	}
	return confTemplate, nil
}

func GetTemplateList() (error, []ConfTemplate) {
	var templateList []ConfTemplate
	if result := db.Select("id", "name", "created_at", "user_name").Order("created_at").Find(&templateList); result.Error != nil {
		return result.Error, nil
	} else {
		return nil, templateList
	}
}

func AddorUpdateConfTemplate(name, content string, userID uint64) error {
	if len(name) < 1 {
		return errors.New("config template name given is blank")
	}

	// get userName
	user := Auth{ID: userID}
	if result := db.Select("user_name").First(&user); result.Error != nil {
		return result.Error
	}

	confTemplate := ConfTemplate{Name: name, Content: content, User: user.UserName}
	result := db.Create(&confTemplate)
	if result.Error != nil {
		return result.Error
	}
	if result.RowsAffected != 1 {
		return errors.New("add or update not affect any record")
	}

	// only keep 7 template history with same name
	var confTemplates []ConfTemplate
	result = db.Select("id").Where("name = ?", name).Order("created_at").Find(&confTemplates)
	if result.Error != nil {
		return errors.New("error when processing query before delete historical template: " + result.Error.Error())
	}
	if result.RowsAffected < 8 {
		return nil
	}
	deleteTemplates := confTemplates[:len(confTemplates)-7]
	result = db.Delete(&deleteTemplates)
	if result.Error != nil {
		return errors.New("error when delete historical template: " + result.Error.Error())
	}

	return nil
}

func DeleteConfKey(keyId uint64) error {
	confKey := ConfKey{ID: keyId}
	result := db.Delete(&confKey)
	if result.Error != nil {
		return result.Error
	}
	return nil
}
